/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						     respawn_point.cpp

	$Header: $

	$NoKeywords: $

 ************************************************************************/

// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "respawn_point.h"

#include <math.h>

#include "adventure_enemy_marker.h"
#include "adventure_map.h"
#include "army.h"
#include "army_array.h"
#include "army_array_ptr.h"
#include "army_ptr.h"
#include "creature_traits.h"
#include "player.h"
#include "player_list.h"
#include "random_monster.h"
#include "streambuf_operators.h"

namespace
{
	t_uint16 const k_current_save_version = 0;
	
	// Limit to how big a stack we will ever respawn. Never respawn a stack that is
	// is larger than a 700 starting-xp stack would have been at this time...
	int const k_max_respawn_starting_xp = 700;
}

// ---------------------------------------------------------------
// ---------------------------------------------------------------
t_respawn_point::t_respawn_point( t_town_type						alignment,
								  int								initial_growing_experience_value,
								  int								initial_max_growth_per_day,
								  int								initial_non_growing_experience_value,
								  bool								initial_on_water,
								  t_uint8							patrol_radius,
								  t_patrol_type						patrol_type,
								  t_adv_map_point					point )
	: m_alignment( alignment ),
	  m_initial_growing_experience_value( initial_growing_experience_value ),
	  m_initial_non_growing_experience_value( initial_non_growing_experience_value ),
	  m_initial_max_growth_per_day( initial_max_growth_per_day ),
	  m_initial_on_water( initial_on_water ),
	  m_patrol_radius( patrol_radius),
	  m_patrol_type( patrol_type ),
	  m_point( point )
{
	if ( m_initial_growing_experience_value > k_max_respawn_starting_xp )
	{
		m_initial_growing_experience_value = k_max_respawn_starting_xp;
		m_initial_non_growing_experience_value = 0;
	}
	else if ( m_initial_growing_experience_value + initial_non_growing_experience_value > k_max_respawn_starting_xp )
	{
		m_initial_non_growing_experience_value = k_max_respawn_starting_xp - m_initial_growing_experience_value;
	}
}


// ---------------------------------------------------------------
// Read in a respawn point
// ---------------------------------------------------------------
t_respawn_point::t_respawn_point( std::streambuf & stream, t_uint16 version )
{
	if ( version > k_current_save_version )
		throw t_streambuf_read_failure();
	
	m_alignment = t_town_type( get< t_uint8 >( stream ) );
	m_initial_growing_experience_value = get< int >( stream );
	m_initial_max_growth_per_day = get< int >( stream );
	m_initial_non_growing_experience_value = get< int >( stream );
	m_initial_on_water = ( get< t_uint8 >( stream ) != 0 );
	m_patrol_radius = get< t_uint8 >( stream );
	m_patrol_type = t_patrol_type( get< t_uint8 >( stream ) );
	stream >> m_point;
}

// Creatures grow 10% per week. This is the daily growth multiplier
static double const k_daily_growth_multiplier = pow( 1.1, 1.0 / 7.0 );
static double const k_daily_growth_fraction = k_daily_growth_multiplier - 1.0;

// ---------------------------------------------------------------
// Return the xp value the original army would have on the given
// day, if it had never entered combat
// ---------------------------------------------------------------
int t_respawn_point::get_current_xp( int current_day ) const
{
	// Growing XP grows 10% per day, up to m_initial_max_growth_per_day
	double grown_xp; 

	grown_xp = m_initial_growing_experience_value;
/*
	grown_xp = m_initial_growing_experience_value 
			   * pow( k_daily_growth_multiplier, current_day );

	if ( grown_xp * k_daily_growth_fraction > m_initial_max_growth_per_day )
	{
		static double const log_multiplier = log( k_daily_growth_multiplier );

		// Growth would have capped out. When?
		double cap_day = log( double( m_initial_max_growth_per_day ) / ( k_daily_growth_fraction * double( m_initial_growing_experience_value ) ) ) / log_multiplier;

		assert( cap_day <= current_day + 0.5 );
		if ( cap_day < 0 )
		{
			assert( k_daily_growth_fraction * double( m_initial_growing_experience_value ) > m_initial_max_growth_per_day );
			cap_day = 0.0;
		}

		if ( cap_day < current_day )
		{
			// Grow exponentially to cap, then linearly
			int new_grown_xp = ( m_initial_growing_experience_value * pow( k_daily_growth_multiplier, cap_day ) ) +
							   ( m_initial_max_growth_per_day * ( current_day - cap_day ) );

			assert( new_grown_xp < grown_xp );

			grown_xp = new_grown_xp;
		}
	}
	*/

	return int( grown_xp + 0.5 ) + m_initial_non_growing_experience_value;
}


// ---------------------------------------------------------------
// Get the save format version number of all respawn points
// Static function
// ---------------------------------------------------------------
t_uint16 t_respawn_point::get_version()
{
	return k_current_save_version;
}

// ---------------------------------------------------------------
// Respawn an army at this respawn point
// ---------------------------------------------------------------
bool t_respawn_point::respawn( t_adventure_map & map )
{
	// Don't place so close to a player army that the player will be forced to attack the
	// respawned army before moving on (e.g. don't put down an army with a player army in
	// its attack radius)
	t_player_list const & players = map.get_players();

	t_player_list::const_iterator player_iter;

	for ( player_iter = players.begin(); player_iter != players.end(); player_iter++ )
	{
		t_player const & player = **player_iter;

		t_army_array const * player_armies = player.get_armies();

		t_army_array::const_iterator army_iter;

		for ( army_iter = player_armies->begin(); army_iter != player_armies->end(); army_iter++ )
		{
			t_level_map_point_2d offset = m_point - (*army_iter)->get_position();

			// This is a crude test, but it will catch all the cases where the respawn
			// would interfere with an existing army. What it does not do is correctly
			// pass some cases where the respawn will not interfere with the player army;
			// that is, it sometimes prevents a respawn which is actually OK. For instance,
			// the route between the two armies might be blocked. However, the 
			// exact test is much more complex and we don't want to bother. In that spirit,
			// we ignore on/off bridge stuff. While it's true that an army in the middle of a
			// span will not be forced to attack a respawn on the ground, it's possible that
			// an army on a ramp might still be affected, and we don't want to spend the time
			// determining an exact answer.
			if (   offset.level == 0 
				&& abs( offset.row ) <= k_army_attack_radius 
				&& abs( offset.column ) <= k_army_attack_radius )
			{
				return false;
			}
		}
	}


	t_creature_type type = map.get_next_month_creature();
	bool is_ocean = map.is_ocean( m_point );
	int xp = get_current_xp( map.get_day() );

	// Amount of xp going to primary creature type -- Leave room for helpers
	int primary_xp;

	if ( is_ocean )
		primary_xp = xp;
	else
		primary_xp = xp * 2 / 3;

	int count = primary_xp / get_traits( type ).experience;

	if ( count < 1 )
		count = 1;

	xp -= count * get_traits( type ).experience;

	t_army_ptr respawned_army = new t_army( type,
											count,									
											m_initial_growing_experience_value,
											m_initial_max_growth_per_day,
											m_initial_non_growing_experience_value,
											m_patrol_type,
											m_patrol_radius,
											m_point );

	int pass = 0;
	while ( respawned_army->preplacement( map, pass ) )
		pass++;

	// Before we put down, is this point blocked?
	if ( map.get_adv_tile( m_point ).blocks_army( *respawned_army, map, m_point.on_bridge ) ) 
		return false;


	respawned_army->place( map, m_point );

	if ( xp > 0 )
	{
		add_random_helpers( respawned_army, is_ocean, xp );
	}

	respawned_army->trigger_events();

	return true;
}

// ---------------------------------------------------------------
// Write out a respawn point
// ---------------------------------------------------------------
bool t_respawn_point::write( std::streambuf & stream ) const
{
	put< t_uint8 >( stream, m_alignment );
	put< int >( stream, m_initial_growing_experience_value );
	put< int >( stream, m_initial_max_growth_per_day );
	put< int >( stream, m_initial_non_growing_experience_value );
	put< t_uint8 >( stream, m_initial_on_water) ;
	put< t_uint8 >( stream, m_patrol_radius );
	put< t_uint8 >( stream, m_patrol_type );
	stream << m_point;

	return true;
}

